语义分析器遍历AST并检查语言的各种语义规则，例如：变量必须在使用前声明，或者变量的类型必须在表达式中兼容。若语义分析器发现可以改进的情况，还可以输出警告。对于示例表达式语言，语义分析器必须检查是否声明了每个使用的变量(这是该语言所要求的)。一个可能的扩展(这里没有实现)是，在声明的变量未使用时输出警告。

语义分析器在Sema类中实现，由semantic()方法执行。下面是完整的Sema.h头文件内容:

\begin{cpp}
#ifndef SEMA_H
#define SEMA_H

#include "AST.h"
#include "Lexer.h"

class Sema {
    public:
    bool semantic(AST *Tree);
};

#endif
\end{cpp}

实现在Sema.cpp文件中。有趣的部分是语义分析，使用访问者实现。其基本思想是，每个声明变量的名称存储在一个集合中。创建集合时，可以检查每个名字的唯一性，再检查给定的名字是否在集合中:

\begin{cpp}
#include "Sema.h"
#include "llvm/ADT/StringSet.h"

namespace {
class DeclCheck : public ASTVisitor {
    llvm::StringSet<> Scope;
    bool HasError;
    enum ErrorType { Twice, Not };
    void error(ErrorType ET, llvm::StringRef V) {
        llvm::errs() << "Variable " << V << " "
                     << (ET == Twice ? "already" : "not")
                     << " declared\n";
        HasError = true;
    }
public:
    DeclCheck() : HasError(false) {}
    bool hasError() { return HasError; }
\end{cpp}

与Parser类中一样，使用一个标志来指示发生了错误。这些名称存储在一个名为Scope的集合中。在包含变量名的Factor节点上，检查变量名是否在集合中:

\begin{cpp}
virtual void visit(Factor &Node) override {
    if (Node.getKind() == Factor::Ident) {
        if (Scope.find(Node.getVal()) == Scope.end())
        error(Not, Node.getVal());
    }
};
\end{cpp}

对于BinaryOp节点，除了两边都存在并访问之外，没什么需要检查:

\begin{cpp}
virtual void visit(BinaryOp &Node) override {
    if (Node.getLeft())
        Node.getLeft()->accept(*this);
    else
        HasError = true;
    if (Node.getRight())
        Node.getRight()->accept(*this);
    else
        HasError = true;
};
\end{cpp}

在WithDecl节点上，填充该集合并开始遍历表达式:

\begin{cpp}
    virtual void visit(WithDecl &Node) override {
        for (auto I = Node.begin(), E = Node.end(); I != E;
            ++I) {
            if (!Scope.insert(*I).second)
            error(Twice, *I);
        }
        if (Node.getExpr())
            Node.getExpr()->accept(*this);
        else
            HasError = true;
    };
};
}
\end{cpp}

semantic()方法只启动树遍历，并返回错误标志:

\begin{cpp}
bool Sema::semantic(AST *Tree) {
    if (!Tree)
        return false;
    DeclCheck Check;
    Tree->accept(Check);
    return Check.hasError();
}
\end{cpp}

还可以做得更多。若没有使用声明的变量，也可以打印警告。我们把这个留给读者当做练习来实现。若语义分析没有错误，则可以从AST生成LLVM IR。这将在下一节中完成。




